from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait  # 显示等待
from selenium.webdriver.support import expected_conditions as ec  # 显示等待的条件
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException, ElementNotVisibleException
import openpyxl

driver = webdriver.Chrome()  # 创建一个浏览器对象


class TrainSpider(object):
    # 定义类属性
    login_url = 'https://kyfw.12306.cn/otn/resources/login.html'  # 登录的页面
    profile_url = 'https://kyfw.12306.cn/otn/view/index.html'  # 个人中心的页面
    left_ticket = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc'  # 车票查询的页面
    confirm_url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'  # 确认乘车人和座席

    # 定义init初始化方法
    def __init__(self, from_station, to_station, train_date, trains, passenger_name_lst):
        self.from_station = from_station
        self.to_station = to_station
        self.train_date = train_date
        self.station_code = self.init_station_code()  # 初始化车站代码字典
        self.trains = trains
        self.passenger_name_lst = passenger_name_lst
        self.selected_no = None
        self.selected_seat = None

    # 登录的方法
    def login(self):
        # 打开登录的页面
        driver.get(self.login_url)
        WebDriverWait(driver, 1000).until(
            ec.url_to_be(self.profile_url)
        )
        print('登录成功')

    # 查询余票
    def search_ticket(self):
        # 打开车票查询的页面
        driver.get(self.left_ticket)
        # 找到出发站、到达站的 隐藏的HTML标签
        from_station_input = driver.find_element(By.ID, 'fromStation')
        to_station_input = driver.find_element(By.ID, 'toStation')
        # 找到出发时间的input标签
        train_date_input = driver.find_element(By.ID, 'train_date')
        # 根据key 获取对应的value
        from_station_code = self.station_code[self.from_station]  # 根据出发地找到出发地的代号
        to_station_code = self.station_code[self.to_station]  # 根据目的地找到目的地的代号
        # 执行js代码
        driver.execute_script('arguments[0].value="%s"' % from_station_code, from_station_input)
        driver.execute_script('arguments[0].value="%s"' % to_station_code, to_station_input)
        driver.execute_script('arguments[0].value="%s"' % self.train_date, train_date_input)
        # 执行点击查询按钮，执行查询操作
        driver.find_element(By.ID, 'query_ticket').click()
        # 解析车次，显示等待
        WebDriverWait(driver, 1000).until(
            ec.presence_of_element_located((By.XPATH, '//tbody[@id="queryLeftTable"]/tr'))
        )
        # 筛选出有数据的tr，去掉属性为datatran的tr
        trains = driver.find_elements(By.XPATH, '//tbody[@id="queryLeftTable"]/tr[not(@datatran)]')
        is_flag = False  # 标记是否有余票，没有余票为False，有余票为True

        while True:
            # 分别遍历每个车次
            for train in trains:
                # print(train.text)
                infos = train.text.replace('\n', ' ').split(' ')
                train_no = infos[0]  # 列表中索引为0的车次
                if train_no in self.trains:
                    # 根据key获取值  席别是一个列表
                    seat_types = self.trains[train_no]
                    for seat_type in seat_types:  # 遍历席位的列表
                        if seat_type == 'O':  # 说明是二等座
                            count = infos[9]  # 索引为9的元素是二等座余票
                            if count.isdigit() or count == '有':
                                is_flag = True
                                break  # 跳出遍历席位的循环
                        elif seat_type == 'M':  # 说明是一等座
                            count = infos[8]  # 索引为8的是一等座
                            if count.isdigit() or count == '有':
                                is_flag = True
                                break  # 跳出遍历席位的循环
                    # 判断是否有余票
                    if is_flag:
                        self.selected_no = train_no
                        # 有票就可以执行单击预订
                        train.find_element(By.XPATH, './/a[@class="btn72"]').click()
                        return  # 退出遍历车次的循环

    # 确认乘车人，订单
    def confirm(self):
        WebDriverWait(driver, 1000).until(
            ec.url_to_be(self.confirm_url)
        )
        # 等待乘车人标签显示出来
        WebDriverWait(driver, 1000).until(
            ec.presence_of_element_located((By.XPATH, '//ul[@id="normal_passenger_id"]/li/label'))
        )
        # 获取所有的乘客人
        passengers = driver.find_elements(By.XPATH, '//ul[@id="normal_passenger_id"]/li/label')
        for passenger in passengers:
            name = passenger.text  # 获取乘车人姓名
            if name in self.passenger_name_lst:
                passenger.click()  # 点击乘车人label标签

        # 确认席位
        seat_select = Select(driver.find_element(By.ID, 'seatType_1'))
        seat_types = self.trains[self.selected_no]  # 根据key获取值，self.trains是要抢票的车次的字典，self.selected_no是要抢票的车次的键
        for seat_type in seat_types:
            try:
                seat_select.select_by_value(seat_type)
                self.selected_seat = seat_type  # 记录有票的座席
            except NoSuchElementException:
                continue
            else:
                break
        WebDriverWait(driver, 1000).until(
            ec.element_to_be_clickable((By.ID, 'submitOrder_id'))
        )
        # 提交订单
        driver.find_element(By.ID, 'submitOrder_id').click()

        # 显示等待，等待到模式对话框窗口出现
        WebDriverWait(driver, 1000).until(
            ec.presence_of_element_located((By.CLASS_NAME, 'dhtmlx_window_active'))
        )
        # 等待按钮可以点击
        WebDriverWait(driver, 1000).until(
            ec.element_to_be_clickable((By.ID, 'qr_submit_id'))
        )
        submit_btn = driver.find_element(By.ID, 'qr_submit_id')
        while submit_btn:
            try:
                submit_btn.click()
                submit_btn = driver.find_element(By.ID, 'qr_submit_id')
            except ElementNotVisibleException:
                break
        print(f'恭喜成功抢到{self.selected_no}的{self.selected_seat}!!!')

    # 负责调用其他方法（组织其他的代码）
    def run(self):
        # 1.登录
        self.login()
        # 2.车票查询
        self.search_ticket()
        # 3.确认乘车人和订单
        self.confirm()

    def init_station_code(self):
        wb = openpyxl.load_workbook('车站代码.xlsx')
        ws = wb.active
        lst = []  # 存储所有车站名称及代号
        for row in ws.rows:  # 遍历所有行
            sub_lst = []  # 用于存储每行中的车站名称，车站代号
            for cell in row:  # 遍历每一行的单元格
                sub_lst.append(cell.value)
            lst.append(sub_lst)
        # print(dict(lst))
        return dict(lst)


# 启动爬虫程序
def start():
    spider = TrainSpider('太原南', '运城北', '2025-01-18', {'D30': ['O', 'M']}, ['夏悠然'])
    spider.run()
    # spider.init_station_code()

    input("程序执行完毕，按回车键退出...")  # 在此处暂停程序，等待用户按回车键


if __name__ == '__main__':
    start()
